#include <gtest/gtest.h>
#include <gmock/gmock.h>

#define private public
#define protected public
#include <material.h>
#undef private
#undef protected

using namespace std;
using ::testing::ContainerEq;


class LinearElasticTest: public::testing::Test
{
    protected:
        // Declares the LinearElastics
        LinearElastic mat1_ = LinearElastic(1);
        LinearElastic mat2_ = LinearElastic(2);
        LinearElastic mat3_ = LinearElastic(3);
        LinearElastic mat4_ = LinearElastic(4);

        // other data
        double stress = 0, strain = 0, ps = 0, alpha = 0, q = 0;

        // You can remove any or all of the following functions if their bodies
        // would be empty.
        LinearElasticTest()
        {
            // You can do set-up work for each test here.
        };

        ~LinearElasticTest()
        {
            // You can do clean-up work that doesn't throw exceptions here.
        };

        // If the constructor and destructor are not enough for setting up
        // and cleaning up each test, you can define the following methods:
        void SetUp() override
        {
            // Code here will be called immediately after the constructor
            // (right before each test).
            cout << "Set Up LinearElastic Test" << endl;
        };

        void TearDown() override
        {
            // Code here will be called immediately after each test (right
            // before the destructor).
        };

        // Class members declared here can be used by all tests in the test
        // suite for Foo.
};


// Tests that the LinearElastic::LinearElastic() method does Abc.
TEST_F(LinearElasticTest, IsEmptyInitiate)
{
    cout << "\n---------- LinearElasticTest IsEmptyInitiate ----------" << endl;
    cout << "check id... " << endl;
    EXPECT_EQ(mat1_.id(), 1);
    EXPECT_EQ(mat2_.id(), 2);
    EXPECT_EQ(mat3_.id(), 3);
    EXPECT_EQ(mat4_.id(), 4);

    cout << "check type... " << endl;
    char type[] = "LinearElastic";
    EXPECT_STREQ(type, mat1_.type_.c_str());

    cout << "check E... " << endl;
    EXPECT_DOUBLE_EQ(0.0, mat1_.E_);

    cout << "check G... " << endl;
    EXPECT_DOUBLE_EQ(0.0, mat1_.G_);

    cout << "check nu... " << endl;
    EXPECT_DOUBLE_EQ(0.0, mat1_.nu_);

    cout << "check density... " << endl;
    EXPECT_DOUBLE_EQ(0.0, mat1_.density_);

    cout << "check ce... " << endl;
    EXPECT_DOUBLE_EQ(0.0, mat1_.ce_);

    cout << "check damp_ratio... " << endl;
    EXPECT_DOUBLE_EQ(0.0, mat1_.damp_ratio_);

    cout << "check damp_coef... " << endl;
    EXPECT_DOUBLE_EQ(0.0, mat1_.damp_coef_);

    cout << "check mu... " << endl;
    EXPECT_DOUBLE_EQ(0.0, mat1_.mu_);

    cout << "check yield_stress... " << endl;
    EXPECT_DOUBLE_EQ(0.0, mat1_.yield_stress_);

    cout << "check limited_tensile_strain... " << endl;
    EXPECT_DOUBLE_EQ(9.99e99, mat1_.limited_tensile_strain_);

    cout << "check limited_compress_strain... " << endl;
    EXPECT_DOUBLE_EQ(-9.99e99, mat1_.limited_compress_strain_);
};


// Tests that the LinearElastic::isFracture() method.
TEST_F(LinearElasticTest, isFracture)
{
    cout << "\n---------- LinearElasticTest isFracture ----------" << endl;
    double val1 = 0.03, val2 = -0.04;
    mat1_.setLimitedStrain(val1, val2);
    EXPECT_FALSE(mat1_.isFractured(0.0));
    EXPECT_FALSE(mat1_.isFractured(0.01));
    EXPECT_FALSE(mat1_.isFractured(-0.01));

    EXPECT_TRUE(mat1_.isFractured(0.03));
    EXPECT_TRUE(mat1_.isFractured(-0.04));

    EXPECT_TRUE(mat1_.isFractured(0.05));
    EXPECT_TRUE(mat1_.isFractured(-0.04));
};


// Tests that the LinearElastic::setE() method.
TEST_F(LinearElasticTest, setE)
{
    cout << "\n---------- LinearElasticTest setE ----------" << endl;
    double val = 2000;
    mat1_.setE(val);
    EXPECT_DOUBLE_EQ(val, mat1_.E_);

    // input an negative value
    val = -100;
    mat1_.setE(val);
    EXPECT_DOUBLE_EQ(val, mat1_.E_);
};


// Tests that the LinearElastic::setG() method does Abc.
TEST_F(LinearElasticTest, setG)
{
    cout << "\n---------- LinearElasticTest setG ----------" << endl;
    double val = 2000;
    mat1_.setG(val);
    EXPECT_DOUBLE_EQ(val, mat1_.G_);

    // input an negative value
    val = -100;
    mat1_.setG(val);
    EXPECT_DOUBLE_EQ(val, mat1_.G_);
};


// Tests that the LinearElastic::setNu() method does Abc.
TEST_F(LinearElasticTest, setNu)
{
    cout << "\n---------- LinearElasticTest setNu ----------" << endl;
    double val = 2000;
    mat1_.setNu(val);
    EXPECT_DOUBLE_EQ(val, mat1_.nu_);
    double G = mat1_.E_ / 2.0 / (1 + val);
    EXPECT_DOUBLE_EQ(G, mat1_.G_);

    // input an negative value
    val = -100;
    mat1_.setNu(val);
    EXPECT_DOUBLE_EQ(val, mat1_.nu_);
    G = mat1_.E_ / 2.0 / (1 + val);
    EXPECT_DOUBLE_EQ(G, mat1_.G_);
};


// Tests that the LinearElastic::setDensity() method does Abc.
TEST_F(LinearElasticTest, setDensity)
{
    cout << "\n---------- LinearElasticTest setDensity ----------" << endl;
    double val = 2000;
    mat1_.setDensity(val);
    EXPECT_DOUBLE_EQ(val, mat1_.density_);

    // input an negative value
    val = -100;
    mat1_.setDensity(val);
    EXPECT_DOUBLE_EQ(val, mat1_.density_);
};


// Tests that the LinearElastic::calcCe() method does Abc.
TEST_F(LinearElasticTest, calcCe)
{
    cout << "\n---------- LinearElasticTest calcCe ----------" << endl;
    // EXPECT_DOUBLE_EQ(0.0/0.0, mat1_.calcCe());
    double E = 2000, density = 20;
    mat1_.setE(E);
    mat1_.setDensity(density);
    EXPECT_DOUBLE_EQ(sqrt(E/density), mat1_.calcCe());
};


// Tests that the LinearElastic::setDampRatio() method does Abc.
TEST_F(LinearElasticTest, setDampRatio)
{
    cout << "\n---------- LinearElasticTest setDampRatio ----------" << endl;
    double val = 2000;
    mat1_.setDampRatio(val);
    EXPECT_DOUBLE_EQ(val, mat1_.damp_ratio_);

    // input an negative value
    val = -100;
    mat1_.setDampRatio(val);
    EXPECT_DOUBLE_EQ(val, mat1_.damp_ratio_);
};


// Tests that the LinearElastic::setDampCoef() method does Abc.
TEST_F(LinearElasticTest, setDampCoef)
{
    cout << "\n---------- LinearElasticTest setDampCoef ----------" << endl;
    double val = 2000;
    mat1_.setDampCoef(val);
    EXPECT_DOUBLE_EQ(val, mat1_.damp_coef_);

    // input an negative value
    val = -100;
    mat1_.setDampCoef(val);
    EXPECT_DOUBLE_EQ(val, mat1_.damp_coef_);
};


// Tests that the LinearElastic::setMu() method does Abc.
TEST_F(LinearElasticTest, setMu)
{
    cout << "\n---------- LinearElasticTest setMu ----------" << endl;
    double val = 2000;
    mat1_.setMu(val);
    EXPECT_DOUBLE_EQ(val, mat1_.mu_);

    // input an negative value
    val = -100;
    mat1_.setMu(val);
    EXPECT_DOUBLE_EQ(val, mat1_.mu_);
};


// Tests that the LinearElastic::setLimitedTensileStrain() method does Abc.
TEST_F(LinearElasticTest, setLimitedTensileStrain)
{
    cout << "\n---------- LinearElasticTest setLimitedTensileStrain ----------" << endl;
    double val = 2000;
    mat2_.setLimitedTensileStrain(val);
    EXPECT_DOUBLE_EQ(val, mat2_.limited_tensile_strain_);

    // input an negative value
    val = -100;
    mat1_.setLimitedTensileStrain(val);
    EXPECT_DOUBLE_EQ(9.99e99, mat1_.limited_tensile_strain_);

};


// Tests that the LinearElastic::setLimitedCompressStrain() method does Abc.
TEST_F(LinearElasticTest, setLimitedCompressStrain)
{
    cout << "\n---------- LinearElasticTest setLimitedCompressStrain ----------" << endl;
    double val = 2000;
    mat1_.setLimitedCompressStrain(val);
    EXPECT_DOUBLE_EQ(-9.99e99, mat1_.limited_compress_strain_);

    // input an negative value
    val = -100;
    mat2_.setLimitedCompressStrain(val);
    EXPECT_DOUBLE_EQ(val, mat2_.limited_compress_strain_);
};


// Tests that the LinearElastic::setLimitedStrain() method does Abc.
TEST_F(LinearElasticTest, setLimitedStrain)
{
    cout << "\n---------- LinearElasticTest setLimitedStrain ----------" << endl;
    double val1 = 1000, val2 = -1000;
    mat1_.setLimitedStrain(val1, val1);
    EXPECT_DOUBLE_EQ(val1, mat1_.limited_tensile_strain_);
    EXPECT_DOUBLE_EQ(-9.99e99, mat1_.limited_compress_strain_);

    mat2_.setLimitedStrain(val1, val2);
    EXPECT_DOUBLE_EQ(val1, mat2_.limited_tensile_strain_);
    EXPECT_DOUBLE_EQ(val2, mat2_.limited_compress_strain_);

    mat3_.setLimitedStrain(val2, val1);
    EXPECT_DOUBLE_EQ(9.99e99, mat3_.limited_tensile_strain_);
    EXPECT_DOUBLE_EQ(-9.99e99, mat3_.limited_compress_strain_);

    mat4_.setLimitedStrain(val2, val2);
    EXPECT_DOUBLE_EQ(9.99e99, mat4_.limited_tensile_strain_);
    EXPECT_DOUBLE_EQ(val2, mat4_.limited_compress_strain_);
};


// Tests that the LinearElastic::Eq() method does Abc.
TEST_F(LinearElasticTest, Eq)
{
    cout << "\n---------- LinearElasticTest Eq ----------" << endl;
    double E = 1000;
    mat1_.setE(E);
    double ds = 0.01;
    double target_stress = 0, target_strain = 0;
    for (int i = 0; i < 10; i++);
    {
        EXPECT_DOUBLE_EQ(E, mat1_.Eq(ds, &stress, &strain, &ps, &alpha, &q));
        target_strain += ds;
        target_stress = E * target_strain;
        EXPECT_DOUBLE_EQ(target_strain, strain);
        EXPECT_DOUBLE_EQ(target_stress, stress);
        EXPECT_DOUBLE_EQ(0, ps);
        EXPECT_DOUBLE_EQ(0, alpha);
        EXPECT_DOUBLE_EQ(0, q);
    }
};


// Tests that the LinearElastic::info() method does Abc.
TEST_F(LinearElasticTest, info)
{
    // do not need to test
};
